#include <iostream>
#include <omp.h>
#include <stack>
#include "../lfqueue/queue.cpp"
#include "universal.h"

using std::cout;
using std::stack;
using std::bind;
using std::placeholders::_1;
using std::ref;

void CallDequeue(size_t *ret, bool *succ, Queue<size_t> *pqueue) {
	*succ = pqueue->Dequeue(ret);
}

template <class T>
void CallStackTop(T *ret, stack<T> *pstack) {
	*ret = pstack->top();
}

int main() {
	const int N = 10;
	omp_set_num_threads(N);
	Queue<size_t> queue[N];
	Universal<Queue<size_t>> univ(N);
	
#pragma omp parallel 
	{
		size_t ret = -1;
		bool succ = false;
		size_t i = omp_get_thread_num();
		univ.Apply(bind(&Queue<size_t>::Enqueue, _1, i + 1));
		univ.Apply(bind(&Queue<size_t>::Enqueue, _1, (i + 1) * 10 + i + 1));
		univ.Apply(bind(CallDequeue, &ret, &succ, _1));
		univ.Commit(queue + i);

	#pragma omp barrier
	#pragma omp critical
		{
			if (succ)
				printf("Thread [%lu] dequeued [%lu]\n", i, ret);
			printf("Queue[%lu]: {", i);
			size_t elem = 0;
			while (queue[i].Dequeue(&elem)) 
				printf("%lu, ", elem);
			printf("}\n");
		}
	}	
	printf("\n\nAnd now stack:\n");

// And Now stack:
	Universal<stack<size_t>> univ_st(N);
	stack<size_t> st[N];

#pragma omp parallel
	{
		size_t top = 0;
		size_t i = omp_get_thread_num();

		typedef void (stack<size_t>::*push_func) (const size_t& val);
		univ_st.Apply(bind((push_func)&stack<size_t>::push, _1, i + 1));
		univ_st.Apply(bind((push_func)&stack<size_t>::push, _1, 11 * (i + 1)));
		univ_st.Apply(bind(CallStackTop<size_t>, &top, _1));
		univ_st.Apply(bind(&stack<size_t>::pop, _1));
		univ_st.Commit(st + i);
		
	#pragma omp barrier
	#pragma omp critical
		{
			printf("Thread [%lu] top [%lu]\n", i, top);
			printf("Stack[%lu]: {", i);
			while (!st[i].empty()) {
				printf("%lu, ", st[i].top());
				st[i].pop();
			}
			printf("}\n");
		}

	}



	return 0;
}
